#include "std/debug.h"
#include "arch/bitops.h"
#include "kernel/irq.h"
#include "kernel/proc.h"
#include "kernel/slab.h"

#ifdef __cplusplus
extern "C" {
#endif

void free_irq(uint irq, void *dev_id)
{
	uint flags;
	irqaction_t **p;
	irq_desc_t *desc;
	 
	if (irq >= NR_IRQS)
		return;

	desc = irq_desc + irq;

	 // need lock
	spin_lock_irqsave(&desc->lock,flags);
	 
	p = &desc->action;
	for (;;)
	{
		irqaction_t *action = *p;
		if (action)
		{
			irqaction_t **pp = p;
			p = &action->next;
			if (action->dev_id != dev_id)
				 continue;
			 
			/* Found it - now remove it from the list of entries */
			*pp = action->next;
			 
			if (!desc->action)
			{
				desc->status |= IRQ_DISABLED;
				desc->handler->shutdown(irq);
			}
			
			spin_unlock_irqrestore(&desc->lock,flags);

			// todo: handle SMP: ifdef CONFIG_SMP
			/* Wait to make sure it's not being used on another CPU */
			// while (desc->status & IRQ_INPROGRESS)
			//	barrier();
			// #endif
			
			if (!(action->flags & IAF_NORELEASE)) kfree(action);
			return;
		}

		spin_unlock_irqrestore(&desc->lock,flags);
		return;
	}
}

// this function assume the "irq" is valid
static int setup_irq(uint irq, irqaction_t *action)
{
	uint flags;
	bool shared = false;
	irqaction_t *old, **p;
	irq_desc_t *desc = irq_desc + irq;

	// todo: randomize
	
	// need lock
	spin_lock_irqsave(&desc->lock, flags);
	
	p = &desc->action;
	if ((old = *p) != NULL)
	{
		/* Can't share interrupts unless both agree to */
		if (!(old->flags & action->flags & SA_SHIRQ))
		{
			spin_unlock_irqrestore(&desc->lock,flags);
			dbg_output3("setup_irq: unable to share interrupt for devid:%u at IRQ%u\n",
				action->dev_id, irq);
			return ERR_BUSY;
		}

		/* add new interrupt at end of irq queue */
		do
		{
			p = &old->next;
			old = *p;
		}
		while (old);
		shared = true;
	}
	
	*p = action;
	
	if (!shared)
	{
		desc->status &= ~(IRQ_DISABLED | IRQ_WAITING);
		kassert(desc->handler && desc->handler->startup);
		desc->handler->startup(irq);
	}

	spin_unlock_irqrestore(&desc->lock,flags);

	dbg_output3("setup_irq: success of devid:%u at IRQ%u\n",
		action->dev_id, irq);
	return 0;
}

int _request_irq(uint irq, irqaction_t *action)
{
	if (NULL == action)
		return ERR_BAD_PARAMETERS;
	if (irq >= NR_IRQS)
		return ERR_OUT_OF_RANGE;
	
	// check action parameters
	if (!(action->flags & IAF_NORELEASE) || NULL == action->handler)
		return ERR_BAD_PARAMETERS;
	
	return setup_irq(irq, action);
}

int request_irq(uint irq, void (*handler)
	(int, void*, regs_t*), uint flags,
	const char *devname, void *dev_id)
{
	int retval;
	irqaction_t *action;

	if (NULL == handler)
		return ERR_BAD_PARAMETERS;
	if (irq >= NR_IRQS)
		return ERR_BAD_PARAMETERS;

	action = kmalloc(sizeof(irqaction_t));
	if (NULL == action)
		return ERR_NOMEMORY;
	
	action->handler = handler;
	action->flags = flags;
	action->name = devname;		// todo: this seems to be a problem
	action->dev_id = dev_id;
	
	retval = setup_irq(irq, action);
	if (retval) kfree(action);

	return retval;
}

static int handle_irq_event(uint irq, regs_t* regs, irqaction_t* action)
{
	int status;
	uint cpu = smp_processor_id();

	irq_enter(cpu, irq);

	status = 1;	/* Force the "do bottom halves" bit */

	if (!(action->flags & SA_INTERRUPT))
		sti();

	do
	{
		status |= action->flags;
		action->handler(irq, action->dev_id, regs);
		action = action->next;
	}
	while (action);
	
	// todo: randomize
	// if (status & SA_SAMPLE_RANDOM)
	//	add_interrupt_randomness(irq);
	
	cli();

	irq_exit(cpu, irq);
	return status;
}

static void do_softirq(void);

void handle_irq(regs_t regs)
{
	uint cpu = smp_processor_id();
	uint irq = (regs.u.irq & 0xff) - 0x30;
	irq_desc_t *desc = irq_desc + irq;
	struct irqaction * action;
	unsigned int status;

	kstat.irqs[cpu][irq]++;
	
	spin_lock(&desc->lock);
	desc->handler->ack(irq);

	status = desc->status & ~(IRQ_WAITING);
	status |= IRQ_PENDING; /* we _want_ to handle it */

	/*
	 * If the IRQ is disabled for whatever reason, we cannot
	 * use the action we have.
	 */
	action = NULL;
	if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))
	{
		action = desc->action;
		status &= ~IRQ_PENDING; /* we commit to handling */
		status |= IRQ_INPROGRESS; /* we are handling it */
	}
	desc->status = status;

	/*
	 * If there is no IRQ handler or it was disabled, exit early.
	   Since we set PENDING, if another processor is handling
	   a different instance of this same irq, the other processor
	   will take care of it.
	 */
	if (!action)
		goto out;

	for (;;) {
		spin_unlock(&desc->lock);
		handle_irq_event(irq, &regs, action);
		spin_lock(&desc->lock);
		
		if (!(desc->status & IRQ_PENDING))
			break;
		desc->status &= ~IRQ_PENDING;
	}
	desc->status &= ~IRQ_INPROGRESS;
out:
	/*
	 * The ->end() handler has to deal with interrupts which got
	 * disabled while the handler was running.
	 */
	desc->handler->end(irq);
	spin_unlock(&desc->lock);

	if (softirq_active(cpu) & softirq_mask(cpu))
		do_softirq();
}

static softirq_action_t softirq_vec[32] = {0};

static void do_softirq(void)
{
	int cpu = smp_processor_id();
	uint active, mask;

	if (in_interrupt())
		return;

	local_tasklet_disable();
	local_irq_disable();
	mask = softirq_mask(cpu);
	active = softirq_active(cpu) & mask;
	
	if (active)
	{
		struct softirq_action *h;

restart:
		
		/* reset active bitmask before enabling irqs */
		softirq_active(cpu) &= ~active;
		local_irq_enable();
		
		h = softirq_vec;
		mask &= ~active;
		
		do
		{
			if (active & 1)
				h->action(h);

			h++;
			active >>= 1;
		}
		while (active);
		
		local_irq_disable();
		
		active = softirq_active(cpu);
		if ((active &= mask) != 0)
			goto restart;
	}
	local_tasklet_enable();
}

DECLARE_PRIVATE_SPINLOCK(softirq_mask_lock);

void open_softirq(int nr, void (*action)(softirq_action_t*), void *data)
{
	uint flags;
	int i;

	kassert(NULL != action);

	// need lock
	spin_lock_irqsave(&softirq_mask_lock, flags);
	softirq_vec[nr].data = data;
	softirq_vec[nr].action = action;
	
	for (i = 0; i<NR_CPUS; i++)
		softirq_mask(i) |= (1 << nr);

	spin_unlock_irqrestore(&softirq_mask_lock, flags);
}

tasklet_t *tasklet_vec[NR_CPUS] = {0};

static void tasklet_action(softirq_action_t *a)
{
	tasklet_t *list;
	int cpu = smp_processor_id();

	local_irq_disable();
	list = tasklet_vec[cpu];
	tasklet_vec[cpu] = NULL;
	local_irq_enable();

	while (list)
	{
		tasklet_t *t = list;
		list = list->next;

		if (tasklet_trylock(t))
		{
			if (!t->count)
			{
				clear_bit(TASKLET_STATE_SCHED, &t->state);
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}
		local_irq_disable();
		t->next = tasklet_vec[cpu];
		tasklet_vec[cpu] = t;
		__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
		local_irq_enable();
	}
}

// global initializtion
void global_init_irq_module(void)
{
	i386_init_traps();
	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
}

#ifdef __cplusplus
}
#endif
/* EOF */
